
//  Copyright (C) 2009-2011 Luca Piccioni
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
// 
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

using Derm.Render;

namespace Derm.Raster
{
	/// <summary>
	/// ImageCodec for EXR file format.
	/// </summary>
	internal class ImageLoaderEXR : ImageCodecPlugin
	{
		#region EXR File Format

		#region File Format Masks

		/// <summary>
		/// EXR file magic number.
		/// </summary>
		private const UInt32 EXR_MAGIC_NUMBER = 0x01312f76;

		/// <summary>
		/// Version mask.
		/// </summary>
		private const UInt32 EXR_VERSION_MASK = 0x000000FF;

		/// <summary>
		/// Flags mask.
		/// </summary>
		private const UInt32 EXR_FLAGS_MASK = 0xFFFFFF00;

		/// <summary>
		/// Tiled imaged flag mask.
		/// </summary>
		private const UInt32 EXR_FLAG_TILED_MASK = 0x00000200;

		#endregion

		#region File Format Data Types

		/// <summary>
		/// EXR data type: integer box.
		/// </summary>
		private const string EXR_DATA_TYPE_BOX2I = "box2i";

		/// <summary>
		/// Box defined by integer numbers.
		/// </summary>
		private struct ExrBox2i
		{
			/// <summary>
			/// Parse a ExrBox2i from a binary stream.
			/// </summary>
			/// <param name="br">
			/// A <see cref="BinaryReader"/> where data is stored. The reader shall be positioned
			/// correctly.
			/// </param>
			public ExrBox2i(BinaryReader br)
			{
				xMin = br.ReadInt32();
				yMin = br.ReadInt32();
				xMax = br.ReadInt32();
				yMax = br.ReadInt32();
			}

			/// <summary>
			/// Box width.
			/// </summary>
			public int Width
			{
				get {
					return (xMax - xMin);
				}
			}

			/// <summary>
			/// Box height.
			/// </summary>
			public int Height
			{
				get {
					return (yMax - yMin);
				}
			}

			/// <summary>
			/// Minimum X coordinate.
			/// </summary>
			public int xMin;
			/// <summary>
			/// Minimum Y coordinate.
			/// </summary>
			public int yMin;
			/// <summary>
			/// Maximum X coordinate.
			/// </summary>
			public int xMax;
			/// <summary>
			/// Maximum Y coordinate.
			/// </summary>
			public int yMax;
		}

		/// <summary>
		/// EXR data type: float box.
		/// </summary>
		private const string EXR_DATA_TYPE_BOX2F = "box2f";

		/// <summary>
		/// Box defined by float numbers.
		/// </summary>
		private struct ExrBox2f
		{
			/// <summary>
			/// Parse a ExrBox2i from a binary stream.
			/// </summary>
			/// <param name="br">
			/// A <see cref="BinaryReader"/> where data is stored. The reader shall be positioned
			/// correctly.
			/// </param>
			public ExrBox2f(BinaryReader br)
			{
				xMin = br.ReadSingle();
				yMin = br.ReadSingle();
				xMax = br.ReadSingle();
				yMax = br.ReadSingle();
			}

			/// <summary>
			/// Minimum X coordinate.
			/// </summary>
			public float xMin;
			/// <summary>
			/// Minimum Y coordinate.
			/// </summary>
			public float yMin;
			/// <summary>
			/// Maximum X coordinate.
			/// </summary>
			public float xMax;
			/// <summary>
			/// Maximum Y coordinate.
			/// </summary>
			public float yMax;
		}

		/// <summary>
		/// EXR data type: channel list.
		/// </summary>
		private const string EXR_DATA_TYPE_CHLIST = "chlist";

		/// <summary>
		/// Channel pixel type.
		/// </summary>
		private enum ExrPixelType
		{
			/// <summary>
			/// Pixel components specified as unsigned integers.
			/// </summary>
			Uint = 0,
			/// <summary>
			/// Pixel components specified as half floats.
			/// </summary>
			Half = 1,
			/// <summary>
			/// Pixel components specified as floats.
			/// </summary>
			Float = 2
		}

		/// <summary>
		/// EXR image channel.
		/// </summary>
		private struct ExrChannel
		{
			/// <summary>
			/// Construct an EXR channel from a stream.
			/// </summary>
			/// <param name="br">
			/// A <see cref="BinaryReader"/> where data is stored. The reader shall be positioned
			/// correctly.
			/// </param>
			public ExrChannel(BinaryReader br)
			{
				Name = ExrReadString(br);
				PixelType = (ExrPixelType)br.ReadInt32();
				Linear = (br.ReadUInt32() & 0xFF0000) != 0;
				xSampling = br.ReadInt32();
				ySampling = br.ReadInt32();
			}

			/// <summary>
			/// Channel name.
			/// </summary>
			public string Name;
			/// <summary>
			/// Channel pixel type.
			/// </summary>
			public ExrPixelType PixelType;
			/// <summary>
			/// Linear color.
			/// </summary>
			public bool Linear;
			/// <summary>
			/// Sampling rate on X coordinate.
			/// </summary>
			public int xSampling;
			/// <summary>
			/// Sampling rate on Y coordinate.
			/// </summary>
			public int ySampling;
		}

		/// <summary>
		/// EXR data type: chromaticities.
		/// </summary>
		private const string EXR_DATA_TYPE_CHROMA = "chromaticities";

		/// <summary>
		/// EXR data type: compression method.
		/// </summary>
		private const string EXR_DATA_TYPE_COMPRESSION = "compression";

		/// <summary>
		/// EXR image compression methods.
		/// </summary>
		private enum ExrCompression
		{
			/// <summary>
			/// No compression.
			/// </summary>
			None = 0,
			/// <summary>
			/// RLE compression.
			/// </summary>
			RLE = 1,
			/// <summary>
			/// 
			/// </summary>
			ZIPS = 2,
			/// <summary>
			/// 
			/// </summary>
			ZIP = 3,
			/// <summary>
			/// 
			/// </summary>
			PIZ = 4,
			/// <summary>
			/// 
			/// </summary>
			PXR24 = 5,
			/// <summary>
			/// 
			/// </summary>
			B44 = 6,
			/// <summary>
			/// 
			/// </summary>
			B44A = 7
		}

		/// <summary>
		/// EXR data type: double number.
		/// </summary>
		private const string EXR_DATA_TYPE_DOUBLE = "double";

		/// <summary>
		/// EXR image mapping.
		/// </summary>
		private const string EXR_DATA_TYPE_ENVMAP = "envmap";

		/// <summary>
		/// EXR environment mapping.
		/// </summary>
		private enum ExrEnvMap
		{
			/// <summary>
			/// Latitude/Longitude mapping.
			/// </summary>
			LatLon = 0,
			/// <summary>
			/// Cube mapping.
			/// </summary>
			Cube = 1
		}

		/// <summary>
		/// EXR data type: float number.
		/// </summary>
		private const string EXR_DATA_TYPE_FLOAT = "float";

		/// <summary>
		/// EXR data type: integer number.
		/// </summary>
		private const string EXR_DATA_TYPE_INT = "int";

		/// <summary>
		/// Key code.
		/// </summary>
		private const string EXR_DATA_TYPE_KEYCODE = "keycode";

		/// <summary>
		///  EXR data type: line ordering enumeration.
		/// </summary>
		private const string EXR_DATA_TYPE_LINEORDER = "lineOrder";

		/// <summary>
		/// EXR image lines ordering.
		/// </summary>
		private enum ExrLineOrder
		{
			/// <summary>
			/// Increasing height.
			/// </summary>
			Increasing,
			/// <summary>
			/// Decreasing height.
			/// </summary>
			Decreasing,
			/// <summary>
			/// Random lines.
			/// </summary>
			Random
		}

		/// <summary>
		/// EXR data type: 3x3 matrix of floats.
		/// </summary>
		private const string EXR_DATA_TYPE_MAT3 = "m33f";

		/// <summary>
		/// EXR data type: 4x4 matrix of floats.
		/// </summary>
		private const string EXR_DATA_TYPE_MAT4 = "m44f";

		/// <summary>
		/// EXR data type: preview image.
		/// </summary>
		private const string EXR_DATA_TYPE_PREVIEW = "preview";

		/// <summary>
		/// EXR data type: rational number.
		/// </summary>
		private const string EXR_DATA_TYPE_RATIONAL = "rational";

		/// <summary>
		/// EXR data type: character string.
		/// </summary>
		private const string EXR_DATA_TYPE_STRING = "string";

		private const string EXR_DATA_TYPE_TILEDESC = "tiledesc";

		private const string EXR_DATA_TYPE_TIMECODE = "timecode";

		/// <summary>
		/// EXR data type: bidimensional vector of integers.
		/// </summary>
		private const string EXR_DATA_TYPE_V2I = "v2i";

		/// <summary>
		/// EXR data type: bidimensional vector of floats.
		/// </summary>
		private const string EXR_DATA_TYPE_V2F = "v2f";

		/// <summary>
		/// EXR data type: tridimensional vector of integers.
		/// </summary>
		private const string EXR_DATA_TYPE_V3I = "v3i";

		/// <summary>
		/// EXR data type: tridimensional vector of floats.
		/// </summary>
		private const string EXR_DATA_TYPE_V3F = "v3f";

		/// <summary>
		/// Read a null terminate character string.
		/// </summary>
		/// <param name="br"></param>
		/// <returns></returns>
		private static string ExrReadString(BinaryReader br)
		{
			const int BUFFER_SIZE = 32;		// Maximum allowed length for null terminated strings

			string s;
			long offset = br.BaseStream.Seek(0, SeekOrigin.Current);
			byte[] iBuffer;
			byte iBufferLen = 0;

			// Read a chunk of data
			iBuffer = br.ReadBytes(BUFFER_SIZE);
			for (; iBufferLen < BUFFER_SIZE; iBufferLen++)
				if (iBuffer[iBufferLen] == 0) break;
			if ((iBufferLen == BUFFER_SIZE) && (iBuffer[BUFFER_SIZE - 1] != 0))
				throw new Exception("String data type not conforming with EXR specification");
			// Extract the string from the buffer
			s = ExrStringEncoding.GetString(iBuffer);
			s = s.Substring(0, iBufferLen);

			br.BaseStream.Seek(offset + s.Length + 1, SeekOrigin.Begin);

			return (s);
		}

		private static ASCIIEncoding ExrStringEncoding = new ASCIIEncoding();

		#endregion

		#region File Format Attributes

		/// <summary>
		/// EXR header attribute.
		/// </summary>
		private struct ExrAttribute
		{
			/// <summary>
			/// Attribute name.
			/// </summary>
			public string Name;
			/// <summary>
			/// Attribute type.
			/// </summary>
			public string Type;
			/// <summary>
			/// Attribute size.
			/// </summary>
			public int Size;
			/// <summary>
			/// Attribute value.
			/// </summary>
			public object Value;
		}

		/// <summary>
		/// EXR required attribute name: channel list.
		/// </summary>
		private const string EXT_ATTR_CHANNELS = "channels";

		/// <summary>
		/// EXR required attribute name: compression method.
		/// </summary>
		private const string EXT_ATTR_COMPRESSION = "compression";

		/// <summary>
		/// EXR required attribute name: data window.
		/// </summary>
		private const string EXT_ATTR_DATA_WINDOW = "dataWindow";

		/// <summary>
		/// EXR required attribute name: display window.
		/// </summary>
		private const string EXT_ATTR_DISP_WINDOW = "displayWindow";

		/// <summary>
		/// EXR required attribute name: line order.
		/// </summary>
		private const string EXT_ATTR_LINE_ORDER = "lineOrder";

		/// <summary>
		/// EXR required attribute name: pixel aspect ratio.
		/// </summary>
		private const string EXT_ATTR_PIXEL_ASPECT_RATIO = "pixelAspectRatio";

		/// <summary>
		/// EXR required attribute name: screen window center.
		/// </summary>
		private const string EXT_ATTR_SCREEN_WINDOW_CENTER = "screenWindowCenter";

		/// <summary>
		/// EXR required attribute name: screen window width.
		/// </summary>
		private const string EXT_ATTR_SCREEN_WINDOW_WIDTH = "screenWindowWidth";

		private static List<ExrAttribute> ExrReadAttributes(BinaryReader br)
		{
			List<ExrAttribute> iAttributes = new List<ExrAttribute>();

			while (br.PeekChar() != 0)
				iAttributes.Add(ExrReadAttribute(br));

			return (iAttributes);
		}

		private static object ExrGetAttribute(List<ExrAttribute> iAttributes, string attrName)
		{
			foreach (ExrAttribute attr in iAttributes)
				if (attr.Name == attrName) return (attr.Value);
			return (null);
		}

		/// <summary>
		/// Read an entire EXR attribute.
		/// </summary>
		/// <param name="br"></param>
		/// <returns></returns>
		private static ExrAttribute ExrReadAttribute(BinaryReader br)
		{
			ExrAttribute iAttribute = new ExrAttribute();
			long offset;

			// Read attribute from EXR image
			iAttribute.Name = ExrReadString(br);
			iAttribute.Type = ExrReadString(br);
			iAttribute.Size = br.ReadInt32();
			// Read attribute value
			offset = br.BaseStream.Seek(0, SeekOrigin.Current);
			iAttribute.Value = ExrDecodeAttributeValue(br, iAttribute.Type, iAttribute.Size);
			br.BaseStream.Seek(offset + iAttribute.Size, SeekOrigin.Begin);

			return (iAttribute);
		}

		/// <summary>
		/// Decode an attribute value, depending on its type.
		/// </summary>
		/// <param name="br"></param>
		/// <param name="type"></param>
		/// <returns></returns>
		private static object ExrDecodeAttributeValue(BinaryReader br, string type, int size)
		{
			#region Primitive Types

			if (type == EXR_DATA_TYPE_DOUBLE)
				return (br.ReadDouble());
			else if (type == EXR_DATA_TYPE_FLOAT)
				return (br.ReadSingle());
			else if (type == EXR_DATA_TYPE_INT)
				return (br.ReadInt32());
			else if (type == EXR_DATA_TYPE_STRING) {
				byte[] buffer = br.ReadBytes(size);

				return (ExrStringEncoding.GetString(buffer));
			}

			#endregion

			#region Basic Types

			else if (type == EXR_DATA_TYPE_BOX2I)
				return (new ExrBox2i(br));
			else if (type == EXR_DATA_TYPE_BOX2F)
				return (new ExrBox2f(br));
			else if (type == EXR_DATA_TYPE_V2I) {
				int x, y;

				x = br.ReadInt32();
				y = br.ReadInt32();

				return (new Vertex2i(x, y));
			} else if (type == EXR_DATA_TYPE_V2F) {
				float x, y;

				x = br.ReadSingle();
				y = br.ReadSingle();

				return (new Vertex2f(x, y));
			} else if (type == EXR_DATA_TYPE_V3I) {
				int x, y, z;

				x = br.ReadInt32();
				y = br.ReadInt32();
				z = br.ReadInt32();

				return (new Vertex3i(x, y, z));
			} else if (type == EXR_DATA_TYPE_V3F) {
				float x, y, z;

				x = br.ReadSingle();
				y = br.ReadSingle();
				z = br.ReadSingle();

				return (new Vertex3f(x, y, z));
			} else if (type == EXR_DATA_TYPE_MAT3) {
				Matrix m = new Matrix(3, 3);

				for (int c = 0; c < 3; c++)
					for (int r = 0; r < 3; r++)
						m[c, r] = br.ReadSingle();

				return (m);
			} else if (type == EXR_DATA_TYPE_MAT4) {
				Matrix m = new Matrix(4, 4);

				for (int c = 0; c < 4; c++)
					for (int r = 0; r < 4; r++)
						m[c, r] = br.ReadSingle();

				return (m);
			}

			#endregion

			#region Enumeration Types

			else if (type == EXR_DATA_TYPE_COMPRESSION)
				return ((ExrCompression)br.ReadChar());
			else if (type == EXR_DATA_TYPE_LINEORDER)
				return ((ExrLineOrder)br.ReadChar());

			#endregion

			#region Complex Data Types

			else if (type == EXR_DATA_TYPE_CHLIST) {
				List<ExrChannel> cList = new List<ExrChannel>();

				while (br.PeekChar() != 0)
					cList.Add(new ExrChannel(br));

				return (cList);
			}

			#endregion

			return (null);
		}

		#endregion

		#region File Format Scanline

		/// <summary>
		/// Scanline data block.
		/// </summary>
		private struct ScanlineBlock
		{
			/// <summary>
			/// 
			/// </summary>
			/// <param name="br"></param>
			/// <param name="cMethod"></param>
			public ScanlineBlock(BinaryReader br, Image iData, ExrCompression cMethod)
			{
				byte[] iBlockData;
				int BlockLinesCount;
				int Size;
				int LineNo;

				// Derives scanline blocks count
				BlockLinesCount = GetBlockLinesCount(cMethod);
				// Read header
				LineNo = br.ReadInt32();
				Size = br.ReadInt32();
				// Read scanline data
				iBlockData = br.ReadBytes(Size);
				// Decode scanline data
				iBlockData = DecodeBlockData(iBlockData, cMethod);
				// Store decoded data
				// ...
			}

			/// <summary>
			/// Determine scanline block lines counts.
			/// </summary>
			/// <param name="cMethod">
			/// </param>
			/// <returns>
			/// 
			/// </returns>
			private static int GetBlockLinesCount(ExrCompression cMethod)
			{
				switch (cMethod) {
					case ExrCompression.B44:
					case ExrCompression.B44A:
					case ExrCompression.PIZ:
						return (32);
					case ExrCompression.PXR24:
					case ExrCompression.ZIP:
						return (16);
					case ExrCompression.ZIPS:
					case ExrCompression.RLE:
					case ExrCompression.None:
					default:
						return (1);
				}
			}

			private static byte[] DecodeBlockData(byte[] iBlockData, ExrCompression cMethod)
			{
				switch (cMethod) {

					case ExrCompression.None:
					default:
						return (iBlockData);
					case ExrCompression.B44:
					case ExrCompression.B44A:
					case ExrCompression.PIZ:
					case ExrCompression.PXR24:
					case ExrCompression.ZIP:
					case ExrCompression.ZIPS:
					case ExrCompression.RLE:
						throw new Exception("EXR scanline compression not supported.");
				}
			}
		}

		#endregion

		#region File Format Compression Methods

		#endregion

		#endregion

		#region ImageCodecPlugin Implementation

		/// <summary>
		/// Plugin name.
		/// </summary>
		public override string Name { get { return ("DermEXR"); } }

		/// <summary>
		/// Determine whether this plugin is available for the current process.
		/// </summary>
		/// <returns>
		/// It returns a boolean value indicating whether the plugin is available for the current
		/// process.
		/// </returns>
		public override bool CheckAvailability()
		{
			return (false);
		}

		/// <summary>
		/// Get a list of image formats supported for reading.
		/// </summary>
		public override IEnumerable<ImageFormat> SupportedReadImageFormats
		{
			get {
				return (sSupportedReadFormats);
			}
		}

		/// <summary>
		/// Get a list of image formats supported for writing.
		/// </summary>
		public override IEnumerable<ImageFormat> SupportedWriteImageFormats
		{
			get {
				return (null);
			}
		}

		/// <summary>
		/// Currently supported image formats for reading.
		/// </summary>
		private static ImageFormat[] sSupportedReadFormats = new ImageFormat[] {
			ImageFormat.Exr
		};

		/// <summary>
		/// Query image file informations.
		/// </summary>
		/// <returns>
		/// A <see cref="ImageInfo"/>
		/// </returns>
		public override ImageInfo QueryImageInfo(Stream fs, ImageCodec.CodecCriteria criteria)
		{
			ImageInfo mInfo = new ImageInfo();

			return (mInfo);
		}

		/// <summary>
		/// Load image pixels from file.
		/// </summary>
		/// <returns>
		/// A <see cref="Image"/>
		/// </returns>
		/// <param name="criteria">
		/// A <see cref="ImageCodec.CodecCriteria"/> specifying parameters for loading an image stream.
		/// </param>
		public override Image Load(Stream fs, ImageCodec.CodecCriteria criteria)
		{
			BinaryReader br = new BinaryReader(fs);
			Image image = null;

			List<ExrAttribute> iAttributes;
			UInt32 iMagic, iVersionField;
			UInt32 iVersion;
			bool tiled = false;

			// Read magic number
			iMagic = br.ReadUInt32();
			if (iMagic != EXR_MAGIC_NUMBER) throw new Exception("Image file is not an EXR image");
			// Read version field
			iVersionField = br.ReadUInt32();

			// Parse version field
			if ((iVersion = iVersionField & EXR_VERSION_MASK) != 2)
				throw new Exception("Unsupported EXR version " + iVersion);
			tiled = (iVersionField & EXR_FLAG_TILED_MASK) != 0;

			// Read EXR header
			iAttributes = ExrReadAttributes(br);
			// Get EXR image attributes
			ExrBox2i dataWindow = (ExrBox2i)ExrGetAttribute(iAttributes, EXT_ATTR_DATA_WINDOW);

			if (tiled == false) {
				// Skip line offset table (load sequentially)
				br.BaseStream.Seek(8 * dataWindow.Width, SeekOrigin.Current);
			} else {
			
			}

			Debug.Assert(image != null, "ImageLoaderEXR.Load");

			return (image);
		}

		/// <summary>
		/// Save image pixels to file.
		/// </summary>
		/// <param name="fs">
		/// A <see cref="System.IO.FileStream"/> which stores the image data.
		/// </param>
		/// <param name="image">
		/// A <see cref="Image"/> holding the data to be stored.
		/// </param>
		/// <param name="criteria">
		/// A <see cref="ImageCodec.CodecCriteria"/> specifying parameters for loading an image stream.
		/// </param>
		public override void Save(Stream fs, Image image, ImageFormat format, ImageCodec.CodecCriteria criteria)
		{

		}

		#endregion
	}
}
